﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.PowerPoint;
using Microsoft.Office.PowerPoint.STAMP.Common;
using Microsoft.Office.PowerPoint.STAMP.Core;
using Microsoft.Office.PowerPoint.STAMP.Presenter;
using Microsoft.Office.PowerPoint.STAMP.Properties;
using Microsoft.Office.PowerPoint.STAMP.View;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using WMPLib;
using Application = Microsoft.Office.Interop.PowerPoint.Application;
using Shape = Microsoft.Office.Interop.PowerPoint.Shape;

namespace Microsoft.Office.PowerPoint.STAMP
{
    /// <summary>
    /// Caption Editor form for entering closed captions.
    /// </summary>
    public partial class CaptionEditor : Form, ICaptionEditorView, IRenderView
    {
        private readonly ClosedCaptionsList _closedCaptionsList;
        private List<string> _validationErrors;
        private readonly CaptionManager _captionManager;
        private readonly ClosedCaptionsSortableBindingList _captionsSortableBindingList;
        private readonly ClosedCaptionsList _deletedClosedCaptionsList;

        private bool _userClicked = false;

        private const string StartTimeColumnName = "StartTime";
        private const string EndTimeColumnName = "EndTime";

        private enum SliderSpeedMapping
        {
            VerySlow = 0,
            Slow = 1,
            Normal = 2,
            Fast = 3,
            Faster = 4
        }

        /// <summary>
        /// Constructor for CaptionEditor. Sets isEditing.
        /// </summary>
        /// <param name="closedCaptionsList">List of caption that have to be loaded in the grid</param>
        /// <param name="validationErrors"></param>
        public CaptionEditor(ClosedCaptionsList closedCaptionsList, List<string> validationErrors)
        {
            Argument.CheckIfNull(closedCaptionsList, "closedCaptionsList");
            Argument.CheckIfNull(validationErrors, "validationErrors");

            _closedCaptionsList = closedCaptionsList;
            _validationErrors = validationErrors;

            InitializeComponent();

            if (!ShapeIsMediaType)
            {
                Close();
            }

            wmpCaptionPlayer.settings.volume = 100;

            InitializeGridRows();
            
            _captionsSortableBindingList = ClosedCaptionsSortableBindingList.Empty;
            _captionsSortableBindingList.ClosedCaptions.Clear();

            _captionManager = new CaptionManager(Slide, Shape);

            if (ShapeIsMediaType && Shape.MediaFormat.IsLinked)
            {
                if (File.Exists(Shape.LinkFormat.SourceFullName))
                {
                    wmpCaptionPlayer.URL = Shape.LinkFormat.SourceFullName;
                    var controls = (IWMPControls3)wmpCaptionPlayer.Ctlcontrols;
                    // Check first to be sure the operation is valid. 
                    if (controls.get_isAvailable("stop"))
                    {
                        controls.stop();
                    }
                }
            }
            else if (ShapeIsMediaType && Shape.MediaFormat.IsEmbedded)
            {
                Presentation tempPresentation = Application.Presentations.Add(MsoTriState.msoFalse);
                try
                {
                    string tempPresentationFile = UtilPowerPoint.CreateTempPresentation(tempPresentation, Slide,
                                                                                        UtilPowerPoint.GetShapeTagValue(Shape, Constants.TempPresentationFile));
                    UtilPowerPoint.SetShapeTag(Shape, Constants.TempPresentationFile, tempPresentationFile);
                    ThisAddIn.AddTempFileNameToList(tempPresentationFile);

                    string tempMediaFile = OpenXMLHelper.GetMediaSavedLocation(tempPresentationFile, Shape.Name, "slide1",
                                                                               UtilPowerPoint.GetShapeTagValue(Shape, Constants.TempMediaFileName));
                    UtilPowerPoint.SetShapeTag(Shape, Constants.TempMediaFileName, tempMediaFile);
                    ThisAddIn.AddTempFileNameToList(tempMediaFile);

                    wmpCaptionPlayer.URL = tempMediaFile;
                    IWMPControls3 controls = (IWMPControls3)wmpCaptionPlayer.Ctlcontrols;
                    // Check first to be sure the operation is valid. 
                    if (controls.get_isAvailable("stop"))
                    {
                        controls.stop();
                    }
                }
                catch (Exception ex)
                {
                    bool rethrow = ExceptionPolicy.HandleException(ex, "Global Policy");
                    if (rethrow)
                        throw;
                }
            }

            _deletedClosedCaptionsList = ClosedCaptionsList.Empty;

            if (_captionManager.HasSavedCaptions)
            {
                btnCreateUpdateCaptions.Text = Resources.CaptionEditor_btnCreateUpdateCaptions_Update_Captions;
                closedCaptionsList = Mapper.ToClosedCaptionsList(_captionManager.CaptionItemsList.CaptionItems);

                LoadClosedCaptions(closedCaptionsList);
                BindList();
            }
            else
            {
                btnCreateUpdateCaptions.Text = Resources.CaptionEditor_btnCreateUpdateCaptions_Create_Captions;

                LoadClosedCaptions(_closedCaptionsList);
                BindList();
            }

            SetTitleToMediaNameAndTime();

            ShowValidationNotes();
        }

        public Application Application
        {
            get { return Globals.ThisAddIn.Application; }
        }

        public Slide Slide
        {
            get { return Application.ActiveWindow.View.Slide; }
        }

        public Shape Shape
        {
            get { return Application.ActiveWindow.Selection.ShapeRange[1]; }
        }

        public ClosedCaptionsList CaptionsList { get; set; }

        private void InitializeGridRows()
        {
            DataGridViewColumn startTimeColumn = gvCaptions.Columns[StartTimeColumnName];
            DataGridViewColumn endTimeColumn = gvCaptions.Columns[EndTimeColumnName];
            startTimeColumn.DefaultCellStyle.FormatProvider = new TimeSpanFormatter();
            endTimeColumn.DefaultCellStyle.FormatProvider = new TimeSpanFormatter();
            startTimeColumn.DefaultCellStyle.Format = Resources.TimeSpanStringFormat;
            endTimeColumn.DefaultCellStyle.Format = Resources.TimeSpanStringFormat;
        }

        private void SetTitleToMediaNameAndTime()
        {
            const string titleToBeAResource = "{0} ({1}) - {2}";
            Text = string.Format(titleToBeAResource, Shape.Name,
                        Shape.MediaFormat.Length.ToTimeSpanString(Resources.TimeSpanStringFormat), Resources.CaptionEditor_Caption_Editor);
        }

        /// <summary>
        /// Checks if the shape has any saved captions.
        /// </summary>
        public bool HasSavedCaptions
        {
            get { return _captionManager.HasSavedCaptions; }
        }

        private void BindList()
        {
            gvCaptions.DataSource = _captionsSortableBindingList.ClosedCaptions;
            gvCaptions.Sort(gvCaptions.Columns[StartTimeColumnName], ListSortDirection.Ascending);
        }

        /// <summary>
        /// Loads the closed captions into the bindinglist of the grid.
        /// </summary>
        /// <param name="captions">List of closed captions.</param>
        private void LoadClosedCaptions(ClosedCaptionsList captions)
        {
            foreach (ClosedCaption closedCaption in captions.ClosedCaptions)
            {
                ResetCaptionDirtyProperties(closedCaption);
                _captionsSortableBindingList.ClosedCaptions.Add(closedCaption);
            }
        }

        private void ResetCaptionDirtyProperties(ClosedCaption caption)
        {
            caption.IsDirtyCaptionText = false;
            caption.IsDirtyEndTime = false;
            caption.IsDirtyStartTime = false;
        }

        private bool ShapeIsMediaType
        {
            get { return (Shape != null && Shape.Type == MsoShapeType.msoMedia); }
        }

        /// <summary>
        /// Adds a new Captions to the grid and sets the Focus on the StartTime cell
        /// </summary>
        private void btnAddCaption_Click(object sender, EventArgs e)
        {
            AddNewCaptionToBoundList();
            gvCaptions.CurrentCell = gvCaptions.Rows[gvCaptions.Rows.Count - 1].Cells[0];
            SetTimeCellToMediaPosition(StartTimeColumnName);
        }

        /// <summary>
        /// Deletes the selected row from the caption grid, adds it to deleted list for further processing when updating the slide captions
        /// </summary>
        private void btnDeleteCaption_Click(object sender, EventArgs e)
        {
            if (gvCaptions.CurrentRow != null && gvCaptions.CurrentRow.Selected)
            {
                _deletedClosedCaptionsList.ClosedCaptions.Add((ClosedCaption)gvCaptions.CurrentRow.DataBoundItem);
                gvCaptions.Rows.Remove(gvCaptions.CurrentRow);
            }
        }

        /// <summary>
        /// Calls the Caption Manager to Render and Save Captions on the presentaion
        /// </summary>
        private void btnCreateUpdateCaptions_Click(object sender, EventArgs e)
        {
            this.Enabled = false;
            try
            {
                wmpCaptionPlayer.Ctlcontrols.stop();

                _captionManager.SetCaptionsListMediaShapeName(Shape.Name, _captionsSortableBindingList);

                // Maps all the grid captions to internal object for rendering
                var captionList = Mapper.ToCaptions(_captionsSortableBindingList);

                if (!_captionManager.Validate(captionList))
                {
                    _validationErrors = _captionManager.ValidationErrors;
                    ShowValidationNotes();

                    this.Enabled = true;
                    return;
                }

                // Sets the mediaShapeName property in the lists
                _captionManager.SetCaptionsListMediaShapeName(Shape.Name, _deletedClosedCaptionsList);


                // Maps the deleted captions & removes them from the presentation
                var deletedCaptions = Mapper.ToCaptions(_deletedClosedCaptionsList.ClosedCaptions.Where(a => !a.IsNew));

                int deletedCaptionCount = deletedCaptions.CaptionItems.Count;
                if (deletedCaptions.CaptionItems.Count > Settings.Default.ProcessingNotificationLimit)
                {
                    Progress formProgress = new Progress(deletedCaptionCount);
                    formProgress.Show(this);

                    for (int i = 0; i < deletedCaptionCount; i++)
                    {
                        _captionManager.DeleteCaption(deletedCaptions.CaptionItems[1]);
                        if (i % 10 == 0)
                        {
                            formProgress.UpdateDeleting(i);
                        }
                    }
                    formProgress.Close();
                }
                else
                {
                    _captionManager.DeleteCaptions(deletedCaptions);
                }


                int captionCount = captionList.CaptionItems.Count;
                if (captionCount > Settings.Default.ProcessingNotificationLimit)
                {
                    Progress formProgress = new Progress(captionCount);
                    formProgress.Show(this);

                    _captionManager.RenderSingleSetup(captionList);

                    for (int i = 0; i < captionCount; i++)
                    {
                        _captionManager.RenderSingleCaptionItem(captionList.CaptionItems[i]);
                        if (i % 10 == 0)
                        {
                            formProgress.UpdateCreating(i);
                        }
                    }
                    _captionManager.SetDisplayAreaAndVisibilitySettings();
                    formProgress.Close();
                }
                else
                {
                    _captionManager.RenderClosedCaptions(captionList);
                }

                // Saves the caption data as xml resource in the presentation
                _captionManager.SaveCaptions();

                _userClicked = false;

                Close();
            }
            catch (Exception ex)
            {
                bool rethrow = ExceptionPolicy.HandleException(ex, "Global Policy");
                if (rethrow)
                    throw;
            }
        }

        private void ShowValidationNotes()
        {
            if (_validationErrors.Count > 0)
            {
                StringBuilder errors = new StringBuilder();
                foreach (string error in _validationErrors)
                {
                    errors.AppendLine(error);
                }
                errors.AppendLine(Environment.NewLine);
                errors.AppendLine(
                    String.Format(Resources.CaptionEditor_btnCreateUpdateCaptions_Click_Validations_Message,
                                  btnCreateUpdateCaptions.Text));
                MessageBox.Show(errors.ToString(),
                                Resources.CaptionEditor_btnCreateUpdateCaptions_Click_Validations_Header,
                                MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        private void btnSort_Click(object sender, EventArgs e)
        {
            gvCaptions.Sort(gvCaptions.Columns[StartTimeColumnName], ListSortDirection.Ascending);
        }

        /// <summary>
        /// Sets the selected rows StartTime
        /// Gets the playback position of the media player
        /// No row selected creates a new row and sets the StartTime and focus
        /// </summary>
        private void btnSetStartTime_Click(object sender, EventArgs e)
        {
            if (gvCaptions.CurrentRow == null)
            {
                AddNewCaptionToBoundList();
            }
            SetTimeCellToMediaPosition(StartTimeColumnName);
        }

        private void AddNewCaptionToBoundList()
        {
            _captionsSortableBindingList.ClosedCaptions.Add(new ClosedCaption
                {
                    StartTime = new TimeSpan(0, 0, 0, 0),
                    EndTime = new TimeSpan(0, 0, 0, 0),
                    IsNew = true
                });
        }

        private void SetTimeCellToMediaPosition(string columnName)
        {
            TimeSpan timeSpan = TimeSpan.FromSeconds(wmpCaptionPlayer.Ctlcontrols.currentPosition);

            gvCaptions.CurrentCell = gvCaptions.CurrentRow.Cells[columnName];
            gvCaptions.CurrentCell.Value = timeSpan;
        }

        /// <summary>
        /// Sets the selected rows EndTime
        /// Gets the playback position of the media player
        /// No row selected creates a new row and sets the EndTime and focus
        /// </summary>
        private void btnSetEndTime_Click(object sender, EventArgs e)
        {
            if (gvCaptions.CurrentRow == null)
            {
                AddNewCaptionToBoundList();
            }
            SetTimeCellToMediaPosition(EndTimeColumnName);
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            _userClicked = true;
            Close();
        }

        private void CaptionEditor_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_userClicked)
            {
                bool closing = false;

                if (HasDirtyCaptions())
                {
                    var dialogResult =
                        MessageBox.Show(Resources.CaptionEditor_CaptionEditor_FormClosing_UnsavedCaptions,
                                        Resources.CaptionEditor_CaptionEditor_FormClosing_UnsavedCaptions_Header, MessageBoxButtons.YesNo);
                    if (dialogResult == DialogResult.No)
                    {
                        closing = true;
                    }
                }

                if (!closing)
                {
                    wmpCaptionPlayer.close();
                }
                e.Cancel = closing;
            }
            else
            {
                wmpCaptionPlayer.close();
            }
        }

        private bool HasDirtyCaptions()
        {
            return _captionsSortableBindingList.ClosedCaptions.Any(caption => caption.IsDirty || caption.IsNew);
        }

        private void gvCaptions_DataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            e.Cancel = true;
            e.ThrowException = false;
            if (gvCaptions.Rows[e.RowIndex].ErrorText != String.Empty && null == e.Exception) return;
            MessageBox.Show(Resources.CaptionEditor_gvCaptions_DataError, Resources.CaptionEditor_gvCaptions_DataError_Header);
            gvCaptions.CancelEdit();
        }

        private void gvCaptions_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
        {
            var formatter = e.CellStyle.FormatProvider as ICustomFormatter;
            if (formatter != null)
            {
                e.Value = formatter.Format(e.CellStyle.Format, e.Value, e.CellStyle.FormatProvider);
                e.FormattingApplied = true;
            }
        }

        private void CaptionEditor_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.F2:
                    SetMediaPlaybackPosition();
                    break;

                case Keys.F3:
                    this.btnSetStartTime_Click(sender, null);
                    break;

                case Keys.F4:
                    this.btnSetEndTime_Click(sender, null);
                    break;

                case Keys.F5:
                    SetFocusCaptionText();
                    break;

                case Keys.F6:
                case Keys.Insert:
                    this.btnAddCaption_Click(sender, null);
                    break;

                case Keys.Delete:
                    this.btnDeleteCaption_Click(sender, null);
                    break;

                case Keys.P:
                    if (e.Control)
                    {
                        ControlP();
                    }
                    break;

                case Keys.G:
                    if (e.Shift && e.Control)
                    {
                        if (trkPlaybackSpeed.Value == (int)SliderSpeedMapping.Fast)
                        {
                            trkPlaybackSpeed.Value = (int)SliderSpeedMapping.Faster;
                        }
                        else
                        {
                            trkPlaybackSpeed.Value = (int)SliderSpeedMapping.Fast;
                        }
                    }
                    break;

                case Keys.N:
                    if (e.Shift && e.Control)
                    {
                        trkPlaybackSpeed.Value = (int)SliderSpeedMapping.Normal;
                    }
                    break;

                case Keys.S:
                    if (e.Shift && e.Control)
                    {
                        if (trkPlaybackSpeed.Value == (int)SliderSpeedMapping.Slow)
                        {
                            trkPlaybackSpeed.Value = (int) SliderSpeedMapping.VerySlow;
                        }
                        else
                        {
                            trkPlaybackSpeed.Value = (int)SliderSpeedMapping.Slow;
                        }
                    }
                    break;
            }
        }

        private void trkPlaybackSpeed_ValueChanged(object sender, EventArgs e)
        {
            switch ( trkPlaybackSpeed.Value )
            {
                case (int)SliderSpeedMapping.VerySlow:
                    PlayBackVerySlow();
                    break;
                case (int)SliderSpeedMapping.Slow:
                    PlayBackSlow();
                    break;
                case (int)SliderSpeedMapping.Normal:
                    PlayBackNormal();
                    break;
                case (int)SliderSpeedMapping.Fast:
                    PlayBackFast();
                    break;
                case (int)SliderSpeedMapping.Faster:
                    PlayBackFaster();
                    break;
            }
        }

        private void PlayBackVerySlow()
        {
            wmpCaptionPlayer.settings.rate = 0.4;
        }

        private void PlayBackSlow()
        {
            wmpCaptionPlayer.settings.rate = 0.7;
        }

        private void PlayBackNormal()
        {
            wmpCaptionPlayer.settings.rate = 1.0;
        }

        private void PlayBackFast()
        {
            wmpCaptionPlayer.settings.rate = 1.3;
        }

        private void PlayBackFaster()
        {
            wmpCaptionPlayer.settings.rate = 1.6;
        }

        private void ControlP()
        {
            var controls = (IWMPControls3)wmpCaptionPlayer.Ctlcontrols;
            if (controls.isAvailable["play"])
            {
                controls.play();
            }
            else
            {
                controls.pause();
            }
        }

        private void SetFocusCaptionText()
        {
            if (gvCaptions.CurrentRow != null)
            {
                gvCaptions.CurrentCell = gvCaptions.CurrentRow.Cells["CaptionText"];
                gvCaptions.BeginEdit(true);

                gvCaptions.CurrentRow.Selected = false;
            }
        }

        private void SetMediaPlaybackPosition()
        {
            var currentPosition = wmpCaptionPlayer.Ctlcontrols.currentPosition;
            if (currentPosition > 2)
                wmpCaptionPlayer.Ctlcontrols.currentPosition = currentPosition - 2;
        }
    }
}